﻿#region License
/* Copyright (C) 2009 Tim Coulter
 * 
 * This file is part of ExtremeML.
 * 
 * ExtremeML is free software: you can redistribute
 * it and/or modify it under the terms of the GNU General Public
 * License, as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
 * the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using ExtremeML.Core;
using ExtremeML.Spreadsheet.Address;
using DocumentFormat.OpenXml.Spreadsheet;

namespace ExtremeML.Spreadsheet
{
	// This is a partial class that adds custom functionality to its auto-generated partner.
	public sealed partial class CellFormulaWrapper
	{
		#region Custom Properties

		private const string ReferenceError = "#REF!";

		/// <summary>
		/// Gets the containing <see cref="CellWrapper"/>.
		/// </summary>
		public CellWrapper Cell
		{
			get { return (CellWrapper)Parent; }
		}

		private List<object> _elements;

		/// <summary>
		/// Gets or sets the contained value.
		/// </summary>
		public string Value
		{
			get { return CombineFormula(); }
			set { IsModified = true; ParseFormula(value); }
		}

		/// <summary>
		/// Gets the <see cref="CellArea"/> that manages range addressing for the shared formulas.
		/// </summary>
		public CellArea CellArea { get; private set; }

		#endregion
		#region Custom Methods


		#endregion
		#region Overrides

		/// <summary>
		/// Implements custom initialization functionality.
		/// </summary>
		protected override void OnCustomInitializing()
		{
			_elements = new List<object>();
			CellArea = new CellArea();
		}

		/// <summary>
		/// Implements custom population functionality.
		/// </summary>
		protected override void OnCustomDecomposing()
		{
			_elements = new List<object>(AddressParser.ParseExpression(Wrapped.Text));
			CellArea = AddressParser.ParseCellArea(Reference);
		}

		/// <summary>
		/// Establishes relationships with other package content objects.
		/// </summary>
		protected override void OnCustomActivating()
		{
			RegisterAddressDependencies();
		}

		/// <summary>
		/// Implements custom persistence functionality.
		/// </summary>
		protected override void OnCustomRecomposing(
			CellFormula wrapped)
		{
			Reference = CellArea.Range;
		}

		/// <summary>
		/// Implements custom persistence functionality.
		/// </summary>
		protected override void OnCustomRecomposed(
			CellFormula wrapped)
		{
			wrapped.Text = CombineFormula();
		}

		/// <summary>
		/// Implements custom deletion functionality.
		/// </summary>
		protected override void OnCustomRemoved()
		{
			ReleaseAddressDependencies();
		}

		/// <summary>
		/// Returns the set of <see cref="IWrapper"/> instances defined as custom properties.
		/// </summary>
		protected override IEnumerable<IWrapper> GetCustomWrapperMembers()
		{
			yield return CellArea;
		}

		#endregion
		#region Implementation Methods

		/// <summary>
		/// Populates the contained element collection from the passed formula expression.
		/// </summary>
		private void ParseFormula(
			string expression)
		{
			ReleaseAddressDependencies();
			_elements.Clear();
			_elements.AddRange(AddressParser.ParseExpression(expression));
			RegisterAddressDependencies();
		}

		/// <summary>
		/// Builds a formula expression from the contained element collection.
		/// </summary>
		/// <returns></returns>
		private string CombineFormula()
		{
			if (_elements.Count == 0)
			{
				return null;
			}
			else
			{
				var builder = new StringBuilder();

				foreach (var element in _elements)
				{
					builder.Append(element.ToString());
				}

				return builder.ToString();
			}
		}


		/// <summary>
		/// Registers addressable contained objects for notification of address changes.
		/// </summary>
		private void RegisterAddressDependencies()
		{
			if (!Cell.Worksheet.WorksheetPart.AddressDependencies.Contains(CellArea))
			{
				Cell.Worksheet.WorksheetPart.AddressDependencies.Add(CellArea);
			}

			foreach (var element in _elements)
			{
				if (typeof(IAddressDependency).IsAssignableFrom(element.GetType()))
				{
					Cell.Worksheet.WorksheetPart.AddressDependencies.Add((IAddressDependency)element);

					if (element is CellAddress)
					{
						((CellAddress) element).CellDeleted += HandleElementDeletion;
					}
					else if (element is CellArea)
					{
						((CellArea)element).RangeDeleted += HandleElementDeletion;
					}
					else if (element is RowSpan)
					{
						((RowSpan)element).SpanDeleted += HandleElementDeletion;
					}
					else if (element is ColumnSpan)
					{
						((ColumnSpan)element).SpanDeleted += HandleElementDeletion;
					}
				}
			}
		}

		/// <summary>
		/// Releases previously registered contained objects from notification of address changes.
		/// </summary>
		private void ReleaseAddressDependencies()
		{
			if (Cell.Worksheet.WorksheetPart.AddressDependencies.Contains(CellArea))
			{
				Cell.Worksheet.WorksheetPart.AddressDependencies.Remove(CellArea);
			}

			foreach (var element in _elements)
			{
				if (typeof(IAddressDependency).IsAssignableFrom(element.GetType()))
				{
					if (Cell.Worksheet.WorksheetPart.AddressDependencies.Contains((IAddressDependency)element))
					{
						Cell.Worksheet.WorksheetPart.AddressDependencies.Remove((IAddressDependency)element);
					}

					if (element is CellAddress)
					{
						((CellAddress)element).CellDeleted -= HandleElementDeletion;
					}
					else if (element is CellArea)
					{
						((CellArea)element).RangeDeleted -= HandleElementDeletion;
					}
					else if (element is RowSpan)
					{
						((RowSpan)element).SpanDeleted -= HandleElementDeletion;
					}
					else if (element is ColumnSpan)
					{
						((ColumnSpan)element).SpanDeleted -= HandleElementDeletion;
					}
				}
			}
		}

		/// <summary>
		/// Handles the deletion event of any addressable element.
		/// </summary>
		private void HandleElementDeletion(
			object sender,
			EventArgs e)
		{
			Cell.Worksheet.WorksheetPart.AddressDependencies.Remove((IAddressDependency) sender);
			_elements.Insert(_elements.IndexOf(sender), ReferenceError);
			_elements.Remove(sender);
		}

		#endregion
		#region Static Methods


		#endregion
	}
}
